package com.jjc.android

import android.app.Activity
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.ImageFormat
import android.hardware.camera2.CaptureRequest
import android.hardware.camera2.CaptureResult
import android.hardware.camera2.TotalCaptureResult
import android.media.Image
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.text.TextUtils
import android.util.Log
import android.widget.TextView
import com.hjq.toast.ToastUtils
import com.jjc.android.activity.PicturePreviewActivity
import com.jjc.android.base.BaseActivity
import com.jjc.android.dialog.LoadingDialog
import com.jjc.android.dialog.ReportingDialog
import com.jjc.android.entity.DetectionInfo
import com.jjc.android.entity.DetectionReportInfo
import com.jjc.android.entity.LzyResponse
import com.jjc.android.net.SimpleHttpCallback
import com.jjc.android.util.BitmapUtils
import com.jjc.android.util.Common
import com.jjc.android.util.paramEx
import com.lxj.xpopup.XPopup
import com.lxj.xpopup.util.XPopupUtils
import com.lzy.okgo.OkGo
import com.otaliastudios.cameraview.*
import com.otaliastudios.cameraview.controls.Engine
import com.otaliastudios.cameraview.controls.PictureFormat
import com.otaliastudios.cameraview.engine.Camera2Engine
import com.otaliastudios.cameraview.engine.action.ActionHolder
import com.otaliastudios.cameraview.engine.action.BaseAction
import com.otaliastudios.cameraview.engine.action.LogAction
import com.otaliastudios.cameraview.frame.Frame
import com.otaliastudios.cameraview.frame.FrameProcessor
import com.otaliastudios.cameraview.size.SizeSelectors
import java.io.File
import java.text.DecimalFormat
import java.util.*
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import java.util.function.ToIntFunction
import java.util.stream.Collectors
import kotlin.math.floor
import kotlin.math.max
import kotlin.math.roundToInt
import kotlin.math.roundToLong

class ImageAnalysisManager(private val activity: Activity,private val detectionInfo: DetectionInfo,private val cameraView: CameraView,private val tvDebugMsg:TextView) : CameraListener(), FrameProcessor {
    private val debugMaxLines = 18 // 日志信息最大行数
    init {
        tvDebugMsg.maxLines = debugMaxLines
        tvDebugMsg.setLines(debugMaxLines)
        tvDebugMsg.height = debugMaxLines * XPopupUtils.dp2px(activity,20f)
        cameraView.addCameraListener(this)
        cameraView.addFrameProcessor(this)
        if(Build.MODEL.lowercase()=="aqm-al00"){
            cameraView.engine = Engine.CAMERA1
        }
    }


    private var onFinishCallback:(()->Unit)? = null
    fun setFinishCallback(call:()->Unit){
        onFinishCallback = call
    }


    private val TAG = "CameraEngine"
    //com.otaliastudios.cameraview.engine.action.LogAction
    private val TAG2 =  "CameraEngine"
    private val mHandle = Handler(Looper.getMainLooper())

    private fun postDelay(action:Runnable){
        mHandle.postDelayed(action,300)
    }

    private fun setDebugMsg(s: String) {
        if(Looper.myLooper()!=Looper.getMainLooper()){
            mHandle.post {
                innerDebugMsg(s)
            }
        }else{
            innerDebugMsg(s)
        }
    }

    private val reportingDialog:ReportingDialog by lazy {
        ReportingDialog(activity).apply {
            XPopup.Builder(activity)
                .isDestroyOnDismiss(true)
                .dismissOnBackPressed(false)
                .dismissOnTouchOutside(false)
                .asCustom(this)
        }
    }

    private fun innerDebugMsg(s: String) {
        var t = tvDebugMsg.text as String
        t += "\n"+s
        val arr = t.split("\n".toRegex()).toTypedArray()
        val msg = listOf(*arr)
            .stream()
            .skip((if(arr.size > debugMaxLines) (arr.size - debugMaxLines-1) else 0).toLong())
            .collect(Collectors.joining("\n"))
        tvDebugMsg.text = msg
    }

    private var isActionTakePicture:AtomicBoolean = AtomicBoolean(false)
    private var isAnalysisingPicture:AtomicBoolean = AtomicBoolean(false)

    private var tcStartTime:Long = 0
    private var tcEndTime:Long = 0
    fun adjustCameraParam(){
        if(!cameraView.isOpened){
            ToastUtils.show(activity.getString(R.string.wait_camera_open_txt))
            return
        }
        showLoadingDialog()
        tvDebugMsg.text = ""
        tcStartTime = System.currentTimeMillis()
        if(cameraView.cameraOptions?.isIsoSupported==true&&cameraView.cameraOptions?.isExposureTimeSupported==true){
            exposureTimeAdjustTimes = 0
            isoAdjustTimes = 0
            cameraView.setAutoMode(true)
            needAdjustIso.set(false)
            isActionTakePicture.set(true)
            isAnalysisingPicture.set(true)
            cameraView.setIso(100)
            setCameraExposureTime(10*1000000,true)
        }else{
            setDebugMsg("该机型不支持手动调节iso与曝光时间，将直接进行拍照获取")
            postDelay {
                realTakePicture()
            }
        }

    }

    private fun realTakePicture(){
        isActionTakePicture.set(false)
        hideLoadingDialog()
        handlePictureFormat()
        onFinishCallback?.invoke()
    }


    fun takePicture(){
        captureTime = System.currentTimeMillis()
        tcEndTime = System.currentTimeMillis()
        cameraView.takePicture()
    }

    private fun isSupportRaw():Boolean{
        val isRawSupport = cameraView.cameraOptions?.supportedPictureFormats?.contains(PictureFormat.DNG)?:false
        GlobalManager.isSupportRaw = isRawSupport
//        return isRawSupport&&!GlobalManager.isSpecialMobileBrands&&!GlobalManager.isNotSupportRaw
        return isRawSupport&&!GlobalManager.isSpecialMobileBrands
    }
    
    private val parentDir: String by lazy { "${activity.externalCacheDir}" }
    private val imageOutputDirectory: String by lazy { "$parentDir/images".also { File(it).mkdirs() } }

    // 根据传入的图像，计算百点光强
    private fun calcARV100Pts(bitmap: Bitmap): Double {
        val w = floor(bitmap.width.toDouble()).toInt()
        val h = floor(bitmap.height.toDouble()).toInt()
        val ps = IntArray(w * h)
        bitmap.getPixels(ps, 0, w, 0, 0, w, h)
        // 得到红色通道最强的100点像素的红色通道值，并计算其均值
        return Arrays.stream(ps)
            .mapToObj { x: Int -> x }
            .map { x: Int -> Color.red(x) }
            .sorted(Comparator.reverseOrder()).limit(100)
            .collect(Collectors.averagingInt(ToIntFunction { x: Int -> ("" + x).toInt() }))
    }
    private val delayParamChangeTime = 5*1000
    private var changeParamTime = 0L;
    override fun oCameraParamChanged(exposureTime: Long,iso:Int,changeType: Int) {
//        Log.e(TAG2,"1曝光时间: $exposureTime"+":${getExposureTimeString(exposureTime)}"+"|iso:$iso")
        if(isActionTakePicture.get()){
            val isFix = TextUtils.equals(getExposureTimeString(exposureTime),getExposureTimeString(preExposureTime))&&preIsoValue==iso
            if(isFix||((System.currentTimeMillis() - changeParamTime)>delayParamChangeTime)){
                //            setDebugMsg("当前实际：曝光指数:${getExposureTimeString(exposureTime)}，ISO:${iso}")
                Log.e(TAG2,"2曝光时间: $exposureTime"+":${getExposureTimeString(exposureTime)}"+"|iso:$iso")
                if (isoAdjustTimes > MAX_ISO_ADJUST_TIMES) {
                    realTakePicture()
                } else {
                    isAnalysisingPicture.set(false)
                }
            }


        }
    }

    private val exposureTimeList:ArrayList<Long> = ArrayList()
    private val isoValueList:ArrayList<Int> = ArrayList()
    override fun onCameraOpened(options: CameraOptions) {
        cameraView.cameraOptions?.exposureTimeRange?.let {range->
            val min_exposure_time = range.lower
            var max_exposure_time = range.upper
            if (GlobalManager.is_samsung_galaxy_s && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                // seems we can get away with longer exposure on some devices (e.g., Galaxy S10e claims only max of 0.1s, but works with 1/3s)
                // but Android 11 on Samsung devices also introduces a bug where manual exposure gets ignored if different to the preview,
                // and since the max preview rate is limited to 1/5s (see max_preview_exposure_time_c), there's no point
                // going above this!
                max_exposure_time = max(max_exposure_time, 1000000000L / 5)
            }
            exposureTimeList.clear()
            exposureTimeList.addAll(GlobalManager.genrateExposureTime(min_exposure_time,max_exposure_time))
            Log.e(TAG, "exposureTimeRange: ${min_exposure_time}-${max_exposure_time}")
        }
        cameraView.cameraOptions?.isoRange?.let { range->
            val min_iso = range.lower
            val max_iso = range.upper
            isoValueList.clear()
            isoValueList.addAll(GlobalManager.genrateIso(min_iso, max_iso))
        }


    }

    private fun handlePictureFormat(){
        if(isSupportRaw()&&!isCameraReSetFormat){
            cameraView.pictureFormat = PictureFormat.DNG
        }else{
            cameraView.pictureFormat = PictureFormat.JPEG
        }
    }

    private var isCameraReSetFormat = false
    override fun onCameraError(exception: CameraException) {
        super.onCameraError(exception)
        Log.e(TAG, "Got CameraException #" + exception.reason+"是否关闭："+cameraView.isOpened)
        if(exception.message?.contains("android.hardware.camera2.impl.CameraCaptureSessionImpl") == true){
            cameraView.setPreviewStreamSize(SizeSelectors.and(SizeSelectors.maxWidth(1000), SizeSelectors.biggest()))
        }else if(exception.reason==2&&!isCameraReSetFormat&&(isSupportRaw()||!cameraView.isOpened)){

            if(isSupportRaw()){
                GlobalManager.isNotSupportRaw = true
            }
            if(!cameraView.isOpened){
                Log.e(TAG, "Got CameraException #" + exception.reason+"已关闭")
                cameraView.open()
            }else{
                cameraView.pictureFormat = PictureFormat.JPEG
                Log.e(TAG, "Got CameraException #" + exception.reason+"格式切换到JPG")
            }
            isCameraReSetFormat = true
        }
        hideLoadingDialog()
        Log.e(TAG, "Got CameraException #" + exception.reason)

    }
    private var captureTime: Long = 0
    override fun onPictureTaken(result: PictureResult) {
        super.onPictureTaken(result)
//        adujstExposure(result)
        val callbackTime = System.currentTimeMillis()
        if (captureTime == 0L) captureTime = callbackTime - 300
        Log.e(TAG,"onPictureTaken called! Launching activity. Delay:"+(callbackTime - captureTime) )
        val tcTime = tcEndTime-tcStartTime
        if(Constants.IS_SJ_VERSION){
            hideLoadingDialog()
            PicturePreviewActivity.pictureResult = result
            val intent = Intent(activity, PicturePreviewActivity::class.java)
            intent.putExtra("delay", callbackTime - captureTime)
            intent.putExtra("tcTime",tcTime)
            intent.putExtra("value",currentAverageOfRedValueOf100Points)
            activity.startActivity(intent)
            activity.finish()
        }else{
            submitPicture(result,tcTime,currentAverageOfRedValueOf100Points)
        }
        captureTime = 0
        Log.e(TAG,"onPictureTaken called! Launched activity.")
    }
    override fun process(frame: Frame) {
        if(isActionTakePicture.get()&&!isAnalysisingPicture.get()){
            if (frame.dataClass === Image::class.java) {
                val image: Image = frame.getData()
                if(frame.format == ImageFormat.YUV_420_888){
                    BitmapUtils.getBitmap(image,frame.rotationToUser)?.let {
                        adujstExposure(it)
                    }
                }else{
                    val buffer = image.planes[0].buffer
                    val bytes = ByteArray(buffer.capacity())
                    buffer[bytes]
                    adujstExposure(bytes)
                }

            }
        }
    }
    private var preExposureTime:Long = 10*1000000
    private fun setCameraExposureTime(exposeTime:Long,isInit:Boolean = false){
        if(isInit){
            setDebugMsg("默认调节曝光指数：${getExposureTimeString(exposeTime)}，ISO:100")
        }else{
            setDebugMsg("第${exposureTimeAdjustTimes+1}调节曝光指数：${getExposureTimeString(exposeTime)}")
        }
        preExposureTime = exposeTime
        cameraView.setExposureTime(exposeTime)
        changeParamTime = System.currentTimeMillis()
    }

    private fun fixExposureTimeValue(exposeTime: Long):Long{
        return searchLong(exposeTime,exposureTimeList)
    }

    private fun fixIsoValue(value:Int):Int{
        return searchInt(value,isoValueList)
    }

    private var preIsoValue = 100
    private fun setCameraIso(value:Int){
        setDebugMsg("第${isoAdjustTimes+1}调节ISO：${value}")
        preIsoValue = value
        cameraView.setIso(value)
        changeParamTime = System.currentTimeMillis()
    }


    private fun adujstExposure(bytes: ByteArray){
        val bitmap = CameraUtils.decodeBitmap(bytes)
        if (bitmap != null) {
            adujstExposure(bitmap)
        }else{
            Log.e(TAG,"Bitmap位图为空")
        }
    }
    private var needAdjustIso:AtomicBoolean = AtomicBoolean(false)
    private var currentAverageOfRedValueOf100Points:Double = 0.0
    private fun adujstExposure(bitmap: Bitmap){
        isAnalysisingPicture.set(true)
        val averageOfRedValueOf100Points = calcARV100Pts(bitmap)
        currentAverageOfRedValueOf100Points = averageOfRedValueOf100Points
//        Log.e(TAG,"平均值：$averageOfRedValueOf100Points")
        Log.e(TAG2,"分析图片：曝光时间: $preExposureTime"+":${getExposureTimeString(preExposureTime)}"+"|iso:$preIsoValue|平均值：$averageOfRedValueOf100Points")
        setDebugMsg("平均值:$averageOfRedValueOf100Points")
        adjustCameraParam(averageOfRedValueOf100Points)

        Log.e(TAG,"曝光时间调参次数：$exposureTimeAdjustTimes ISO调参次数：$isoAdjustTimes | 曝光时间: $preExposureTime\"+\":${
            getExposureTimeString(
                preExposureTime
            )
        }\"+\"|iso:$preIsoValue")
    }

    private fun adjustCameraParam(averageOfRedValueOf100Points:Double){
        if(averageOfRedValueOf100Points in 128.0..180.0){
            setDebugMsg("平均值符合预期，执行最终操作")
            realTakePicture()
            return
        }
        if(!needAdjustIso.get()&&exposureTimeAdjustTimes<MAX_EXPOSURE_TIME_ADJUST_TIMES){
            val adjustExposureTime = fixExposureTimeValue(if(averageOfRedValueOf100Points<=20){
                preExposureTime*2
            }else if(averageOfRedValueOf100Points < 128){
                ((154 / averageOfRedValueOf100Points) * preExposureTime).roundToLong()
            }else{
                preExposureTime/2
            })
            if(adjustExposureTime == preExposureTime){
                setDebugMsg("预调节曝光时间相同，开始调节ISO")
                needAdjustIso.set(true)
                adjustCameraParam(averageOfRedValueOf100Points)
            }else{
                setCameraExposureTime(adjustExposureTime)
                exposureTimeAdjustTimes++
            }
        }else if(isoAdjustTimes<MAX_ISO_ADJUST_TIMES){
            val adjustIsoValue = fixIsoValue(if(averageOfRedValueOf100Points<=21){
                preIsoValue*2
            }else if(averageOfRedValueOf100Points < 128){
                ((154 / averageOfRedValueOf100Points) * preIsoValue).roundToInt()
            }else{
                preIsoValue/2
            })
            if(adjustIsoValue==preIsoValue){
                setDebugMsg("预调节ISO相同，执行最终操作")
                isoAdjustTimes = MAX_ISO_ADJUST_TIMES
                realTakePicture()
            }else{
                setCameraIso(adjustIsoValue)
                isoAdjustTimes++
            }
        }else{
            isoAdjustTimes = MAX_ISO_ADJUST_TIMES
            setDebugMsg("曝光时间与ISO调节效果不佳，执行最终操作")
            realTakePicture()
        }
    }

    private var exposureTimeAdjustTimes = 0
    private var isoAdjustTimes = 0
    private val MAX_EXPOSURE_TIME_ADJUST_TIMES = 5
    private val MAX_ISO_ADJUST_TIMES = 5
    private var progressDialog:LoadingDialog? = null

    private fun showLoadingDialog(){
        try {
            if(progressDialog == null){
                progressDialog = LoadingDialog(activity,activity.getString(R.string.camera_para_loading_txt))
            }
            progressDialog?.show()
        }catch (e:Exception){
            e.printStackTrace()
        }
    }

    private fun hideLoadingDialog(){
        try {
            if(progressDialog!=null&&progressDialog!!.isShowing){
                progressDialog?.dismiss()
            }
        }catch (e:Exception){
            e.printStackTrace()
        }

    }
    private val decimal_format_1dp = DecimalFormat("#.#")
    private fun getExposureTimeString(exposure_time: Long): String {
        /*if( MyDebug.LOG )
            Log.d(TAG, "getExposureTimeString(): " + exposure_time);*/
        val exposure_time_s = exposure_time / 1000000000.0
        val string: String
        string = if (exposure_time > 100000000) {
            // show exposure times of more than 0.1s directly
            decimal_format_1dp.format(exposure_time_s) + "s"
        } else {
            val exposure_time_r = 1.0 / exposure_time_s
            " 1/" + (exposure_time_r + 0.5).toInt() + "s"
        }
        /*if( MyDebug.LOG )
            Log.d(TAG, "getExposureTimeString() return: " + string);*/return string
    }

    private fun searchInt(value: Int, a: List<Int>): Int {
        if (value < a[0]) {
            return a[0]
        }
        if (value > a[a.size - 1]) {
            return a[a.size - 1]
        }
        var lo = 0
        var hi = a.size - 1
        while (lo <= hi) {
            val mid = (hi + lo) / 2
            if (value < a[mid]) {
                hi = mid - 1
            } else if (value > a[mid]) {
                lo = mid + 1
            } else {
                return a[mid]
            }
        }
        return if (a[lo] - value < value - a[hi]) a[lo] else a[hi]
    }

    private fun searchLong(value: Long, a: List<Long>): Long {
        if (value < a[0]) {
            return a[0]
        }
        if (value > a[a.size - 1]) {
            return a[a.size - 1]
        }
        var lo = 0
        var hi = a.size - 1
        while (lo <= hi) {
            val mid = (hi + lo) / 2
            if (value < a[mid]) {
                hi = mid - 1
            } else if (value > a[mid]) {
                lo = mid + 1
            } else {
                return a[mid]
            }
        }
        return if (a[lo] - value < value - a[hi]) a[lo] else a[hi]
    }


    private fun submitPicture(pictureResult: PictureResult,tcTime:Long,avaValue:Double){
        cameraView.destroy()
        val extension = when (pictureResult.format) {
            PictureFormat.JPEG -> "jpg"
            PictureFormat.DNG -> "dng"
            else -> throw RuntimeException("Unknown format.")
        }
        val imageFile = File(imageOutputDirectory, "${UserManager.fileName()}.$extension")
        pictureResult.toFile(imageFile){file->
            if(file?.exists()==true){
                hideLoadingDialog()
                submitFile(extension,imageFile,tcTime)
            }else{
                ToastUtils.show(activity.getString(R.string.save_file_error_txt))
                hideLoadingDialog()
                activity.finish()
            }
        }
    }

    private fun submitFile(extension:String,imageFile:File,tcTime:Long){
        reportingDialog.show()
        val currentTime = System.currentTimeMillis()
        if(BuildConfig.DEBUG){
            ToastUtils.show(imageFile.absolutePath)
        }
        GlobalManager.uploadFile(activity as BaseActivity,false,imageFile,{error->
            ToastUtils.show("${activity.getString(R.string.upload_file_error_txt)}：$error")
            reportingDialog.dismiss()
            activity.finish()
//            submitReport(extension,imageFile,tcTime,"",0)
        }){imageName,imageUrl->
//            ToastUtils.show("上传成功：$imageUrl")
            val uploadTime = System.currentTimeMillis() - currentTime
            submitReport(extension,imageName,tcTime,imageUrl,uploadTime)
        }
    }

    private fun submitReport(extension:String,imageName:String,tcTime:Long,remoteFileUrl:String,uploadTime:Long){
        val isDng = if(extension=="dng") 1 else 0
        OkGo.post<LzyResponse<DetectionReportInfo>>(Constants.addDetectionRecord)
            .paramEx(mutableMapOf<String,Any?>(
                "phoneModel" to GlobalManager.deviceInfo(),
                "tuoTuToken" to UserManager.userInfo().tuoTuToken,
                "tuoTuUserId" to UserManager.userId(),
                "detectionUserId" to detectionInfo.detectionUserId,
                "minorTermId" to detectionInfo.minorTermId,
                "minorTermName" to detectionInfo.minorTermName,
                "minorTermEnglishName" to detectionInfo.minorTermEnglishName,
                "detectionUserName" to detectionInfo.detectionUserName,
                "gender" to detectionInfo.gender,
                "birthday" to detectionInfo.birthday,
                "age" to detectionInfo.age,
                "height" to detectionInfo.height,
                "weight" to detectionInfo.weight,
                "deviceId" to detectionInfo.deviceId,
                "stickerId" to detectionInfo.stickerId,
                "waitTime" to detectionInfo.waitTime,
                "measLowEnd" to detectionInfo.measLowEnd,
                "measHighEnd" to detectionInfo.measHighEnd,
                "imageFilename" to imageName,
                "measUnit" to detectionInfo.measUnit,
                "volumeUnit" to detectionInfo.volumeUnit,
                "collectDate" to Common.sdf.format(Date())).also {
                    if(!detectionInfo.exInputs.isNullOrEmpty()){
                        detectionInfo.exInputs.forEach { pair->
                            it.put(pair.key,pair.value)
                        }
                    }
            })
            .tag(activity)
            .execute(object : SimpleHttpCallback<LzyResponse<DetectionReportInfo>>(false){
                override fun onCallSuccess(response: LzyResponse<DetectionReportInfo>) {
                    ToastUtils.show(activity.getString(R.string.upload_success_txt))
                    reportingDialog.dismiss()
                    activity.setResult(Activity.RESULT_OK,Intent().apply {
                        putExtra(Constants.BUNDLE_INFO,response.data.detectionId)
                    })
                    activity.finish()

                }

                override fun onCallError(error: String?) {
                    super.onCallError(error)
                    activity.setResult(Activity.RESULT_OK,Intent().apply {
                        putExtra(Constants.BUNDLE_INFO,"-1")
                    })
                    reportingDialog.dismiss()
                    activity.finish()
                }
            })
    }

}